"""
This module contains helper functions for controlling caching. It does so by
managing the "Vary" header of responses. It includes functions to patch the
header of response objects directly and decorators that change functions to do
that header-patching themselves.

For information on the Vary header, see RFC 9110 Section 12.5.5.

Essentially, the "Vary" HTTP header defines which headers a cache should take
into account when building its cache key. Requests with the same path but
different header content for headers named in "Vary" need to get different
cache keys to prevent delivery of wrong content.

An example: i18n middleware would need to distinguish caches by the
"Accept-language" header.
"""
import threading,hashlib
from cy_query.cache.cache_query import CacheQuerySet
from cy_query.cache.utils.exceptions import CacheError, CacheDataError
from functools import wraps
from cy_query.cache import caches

__all__ = ['Cache','GlobalCache']


class Cache:
    API_KEY = 'loc_memcache::'
    FUNCTION_KEY = 'loc_memcache::function::'
    CACHE_FUNCTION_KEY = 'loc_memcache::cache_function::'
    try:
        cache = caches['dataCache']
    except:
        cache = None

    @staticmethod
    def get_function_key(key,func, args, kwargs):
        """生成安全的函数缓存键"""
        func_key = key + func.__module__ + func.__name__ + '::'

        # 安全处理参数
        param_hash = hashlib.md5()
        for i, arg in enumerate(args):
            param_hash.update(f"arg_{i}={str(arg)}".encode())

        for k, v in sorted(kwargs.items()):
            param_hash.update(f"{k}={str(v)}".encode())

        return func_key + param_hash.hexdigest()


    @staticmethod
    def function(timeout: int = 300):
        """
        函数缓存,必须要有返回值
        :param timeout:
        :return:
        """

        def actual_decorator(function):
            @wraps(function)
            def wrapper_function(*args, **kwargs):
                function_key = Cache.get_function_key(Cache.FUNCTION_KEY,function, args, kwargs)
                data = Cache.cache.get(function_key)
                if data is not None:
                    return data
                result = function(*args, **kwargs)
                if result is None:
                    raise CacheError("缓存函数必须要返回结果")
                Cache.cache.set(function_key, result, timeout=300 if callable(timeout) else timeout)
                return result

            def delete_key(*args, **kwargs):
                key = Cache.get_function_key(Cache.FUNCTION_KEY,function, args, kwargs)
                return Cache.cache.delete(key)

            def delete_key_all():
                pattern = Cache.FUNCTION_KEY + function.__module__ + function.__name__ + '::*'
                return Cache.cache.delete_many(Cache.cache.keys(pattern))

            wrapper_function.delete_key = delete_key
            wrapper_function.delete_key_all = delete_key_all

            return wrapper_function
        if callable(timeout):
            return actual_decorator(timeout)
        return actual_decorator

    @staticmethod
    def cache_queryset_function(timeout: int = 300):
        """
        函数缓存,必须要是CacheQuerySet类型
        :param timeout:
        :return:
        """

        def actual_decorator(function):
            @wraps(function)
            def wrapper_function(*args, **kwargs):
                function_key = Cache.get_function_key(Cache.CACHE_FUNCTION_KEY,function, args, kwargs)
                data = Cache.cache.get(function_key)
                if data is not None:
                    return CacheQuerySet(data)
                result = function(*args, **kwargs)
                if result is None:
                    raise CacheError("缓存函数必须要返回结果")
                if not isinstance(result,CacheQuerySet):
                    try:
                        result = CacheQuerySet(result)
                    except CacheDataError:
                        raise CacheError("缓存函数必须要返回结果必须是CacheQuerySet")
                Cache.cache.set(function_key, result.tolist(), timeout=300 if callable(timeout) else timeout)
                return result

            def delete_key(*args, **kwargs):
                key = Cache.get_function_key(Cache.CACHE_FUNCTION_KEY,function, args, kwargs)
                return Cache.cache.delete(key)

            def delete_key_all():
                pattern = Cache.CACHE_FUNCTION_KEY + function.__module__ + function.__name__ + '::*'
                return Cache.cache.delete_many(Cache.cache.keys(pattern))

            wrapper_function.delete_key = delete_key
            wrapper_function.delete_key_all = delete_key_all

            return wrapper_function

        if callable(timeout):
            return actual_decorator(timeout)
        return actual_decorator


class GlobalCache:
    def __init__(self):
        self.cache = {}
        self.lock = threading.Lock()

    def get(self, key, default=None):
        with self.lock:
            return self.cache.get(key, default)

    def set(self, key, value):
        with self.lock:
            self.cache[key] = value

    def delete(self, key):
        with self.lock:
            if key in self.cache:
                del self.cache[key]

    def all(self):
        return self.cache


